package com.xinlianshiye.im.websocket;

import com.xinlianshiye.clouds.common.exception.WebMessageExceptionBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class DispatcherWebSocket implements ApplicationContextAware, InitializingBean {

    @Autowired
    private WebMessageExceptionBuilder exceptionBuilder;

    private static final Map<WebSocketRequestMappingInfo, WebSocketHandlerMethod> handlerMethodMap = new HashMap<>();

    /**
     * 向注册表中注册 HandlerMethod
     * */
    private void registerHandlerMethod(WebSocketRequestMappingInfo mappingInfo, Object handler, Method method) {
        WebSocketRequestMappingInfo.registerMappingInfo(mappingInfo);
        if (findHandlerMethod(mappingInfo) != null) {
            log.error("MappingInfo[url: {}] 出现了冲突", mappingInfo.getUrl());
        }
        handlerMethodMap.put(mappingInfo, new WebSocketHandlerMethod(handler, method, exceptionBuilder));
    }

    /**
     * 从注册表中查找符合条件的 HandlerMethod
     * 如果符合条件的 HandlerMethod 不存在则返回 null
     * */
    public WebSocketHandlerMethod findHandlerMethod(String url) {
        WebSocketRequestMappingInfo mappingInfo = WebSocketRequestMappingInfo.findMappingInfo(url);
        return mappingInfo == null ? null : findHandlerMethod(mappingInfo);
    }

    /**
     * 从注册表中查找符合条件的 HandlerMethod
     * 如果符合条件的 HandlerMethod 不存在则返回 null
     * */
    private WebSocketHandlerMethod findHandlerMethod(WebSocketRequestMappingInfo mappingInfo) {
        if (handlerMethodMap.containsKey(mappingInfo)) {
            return handlerMethodMap.get(mappingInfo);
        }
        return null;
    }

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }

    protected void initHandlerMethods() {
        for (String beanName : applicationContext.getBeanNamesForType(Object.class)) {
            processCandidateBean(beanName);
        }
    }

    protected void processCandidateBean(String beanName) {
        Class<?> beanType = applicationContext.getType(beanName);
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }

    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ? applicationContext.getType((String) handler) : handler.getClass());
        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            Map<Method, WebSocketRequestMappingInfo> methods = MethodIntrospector.selectMethods(userType, WebSocketRequestMappingInfo::createRequestMappingInfo);
            methods.forEach((method, mapping) -> registerHandlerMethod(mapping, applicationContext.getBean(handlerType), method));
        }
    }

    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, WebSocketController.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, WebSocketRequestMapping.class));
    }
}